package org.snackframework.mapping;

import java.io.File;
import java.io.FileFilter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletContext;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.snackframework.annotation.Autowired;
import org.snackframework.annotation.Controller;
import org.snackframework.annotation.UrlMapping;
import org.snackframework.core.MethodMeta;
import org.snackframework.core.ParameterMeta;
import org.snackframework.core.ThroughUrlCacheElement;
import org.snackframework.core.ViewMetaData;
import org.snackframework.core.ViewType;
import org.snackframework.util.MethodUtils;
import org.snackframework.util.PathUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

public class DefaultAnnotationMapping<K,V> implements AnnotationMapping<K,V>
{
	private static Log log=LogFactory.getLog(DefaultAnnotationMapping.class);
	
	/**
	 * cache object k:url v:ThroughUrlCache
	 */
	private static Map annotationMap=Collections.synchronizedMap(new HashMap<String,ThroughUrlCacheElement>());
	
	/**
	 * scan package
	 */
	private String[] packages;
	
	private ClassLoader classLoader;
	
	public DefaultAnnotationMapping(ClassLoader classLoader)
	{
		this.classLoader=classLoader;
	}
	
	public DefaultAnnotationMapping(String[] packages)
	{
		this.packages=packages;
	}
	
	@Override
	public void setPackages(String[] packages)
	{
		this.packages=packages;
	}
	
	@Override
	public Object get(K key)
	{
		return annotationMap.get(key);
	}

	@Override
	public void put(K key, V value)
	{
		annotationMap.put(key, value);
	}

	@Override
	public void initMapping(ServletContext servletContext)
	{
		if(ArrayUtils.isEmpty(this.packages))
		{
			log.info("DefaultAnnotationMapping packages null , skip initMapping.");
			return ;
		}
		
		//scan packages
		for(String pack:this.packages)
		{
			String realPath=PathUtils.getClassPath(servletContext, pack);
			
			File file=new File(realPath);
			if(!file.isDirectory())
			{
				throw new RuntimeException("请检查配置文件中 annotationScanDirectory 属性的值是否是一个文件目录,包名!而不应该是一个具体的类名!");
			}
			
			scanPackage(classLoader,file,servletContext);
		}
	}
	
	protected void scanPackage(ClassLoader cl,File file,ServletContext servletContext)
	{
		File[] fileList=file.listFiles(new FileFilter(){
			@Override
			public boolean accept(File pathname)
			{
				if(pathname.isDirectory() || -1!=pathname.getName().indexOf(".class"))
				{
					return true;
				}
				return false;
			}
		});
		
		for(File f:fileList)
		{
			if(f.isFile())
			{
				analyseClass(f,servletContext);
			}
			
			else if(f.isDirectory())
			{
				scanPackage(cl,f,servletContext);
			}
		}
	}
	
	protected void analyseClass(File file,ServletContext servletContext)
	{
		try
		{
			Class clazz=classLoader.loadClass(PathUtils.getFilePath(file));
			
			//FIXME Controller 是由那个ClassLoader加载的？？？？  此处应该有个策略的问题，考虑此处可能存在其他annotation的情况！！！！
			Controller annotaion=(Controller)clazz.getAnnotation(Controller.class);
			
			//FIXME 没有 @controller的该如何处理?忽略吗?
			if(null==annotaion)
			{
				return ;
			}
			
			// analyse class(模板对应的处理java文件的class文件)
			ViewMetaData viewMetaData=new ViewMetaData();
			viewMetaData.setViewClass(clazz);
			viewMetaData.setViewType(ViewType.BY_ANNOTATION);
			viewMetaData.setViewClassName(clazz.getName());
			
			// analyse field
			Field[] fields=clazz.getDeclaredFields();
			for(Field field:fields)
			{
				analyseField(field,viewMetaData,servletContext);
			}
			
			// analyse method
			Method[] methods=clazz.getDeclaredMethods();
			for(Method method:methods)
			{
				analyseMethod(method,viewMetaData);
			}
		}
		catch(ClassNotFoundException e)
		{
			log.error("",e);
			e.printStackTrace();
		} 
	}
	
	protected void analyseMethod(Method method, ViewMetaData viewMetaData)
	{
		UrlMapping annotation=(UrlMapping)method.getAnnotation(UrlMapping.class);
		if(null==annotation)
		{
			return ;
		}
		
		// analyse method
		MethodMeta methodMeta=new MethodMeta();
		String url=annotation.value();
		
		Class[] paramTypes=method.getParameterTypes();
		String[] paramNames=MethodUtils.getParameterNames(method);
		if(paramTypes.length!=paramNames.length)
		{
			log.error("参数个数与参数类型数目不相等");
			throw new RuntimeException("参数个数与参数类型数目不相等");
		}
		ParameterMeta[] parameterMetas=new ParameterMeta[paramNames.length];
		for(int i=0;i<paramNames.length;i++)
		{
			ParameterMeta parameterMeta=new ParameterMeta();
			parameterMeta.setParameterName(paramNames[i]);
			parameterMeta.setType(paramTypes[i]);
			parameterMetas[i]=parameterMeta;
		}
		
		methodMeta.setMethod(method);
		methodMeta.setModifiers(method.getModifiers());
		methodMeta.setMethodName(method.getName());
		methodMeta.setParams(parameterMetas);
		methodMeta.setReturnType(method.getReturnType());
		
		annotationMap.put(url, new ThroughUrlCacheElement(viewMetaData,methodMeta));
	}

	protected void analyseField(Field field,ViewMetaData viewMetaData,ServletContext servletContext)
	{
		Autowired annotation=field.getAnnotation(Autowired.class);
		if(null==annotation || field.getModifiers()==Modifier.FINAL)
		{
			return ;
		}
		
		WebApplicationContext webApplicationContext=WebApplicationContextUtils.getWebApplicationContext(servletContext);
		Object value=webApplicationContext.getBean(field.getName());
		
		try
		{
			if(viewMetaData.getViewObject()==null)
			{
				viewMetaData.initializeViewObject();
			}
			
			field.setAccessible(true);
			field.set(viewMetaData.getViewObject(), value);
		}
		catch(IllegalAccessException e)
		{
			if(log.isDebugEnabled())
				e.printStackTrace();
			log.error("反射实例化非法访问",e);
		}
		catch(InstantiationException e)
		{
			if(log.isDebugEnabled())
				e.printStackTrace();
			log.error("反射实例化失败",e);
		}
	}
}
